﻿using Bnbjoy.Business.Abstract;
using Bnbjoy.Business.Concrete;
using Bnbjoy.Business.Constants;
using Bnbjoy.Domain.Abstract;
using Bnbjoy.Domain.Concrete;
using Bnbjoy.Domain.Entities;
using BnbjoyBackend.Api.Constants;
using BnbjoyBackend.Api.Security;
using Microsoft.Owin.Security;
using Microsoft.Owin.Security.OAuth;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Security.Claims;
using System.Threading.Tasks;
using System.Web;

namespace BnbjoyBackend.Api.Provider
{
    public class SimpleAuthorizationServerProvider : OAuthAuthorizationServerProvider
    {
        public override async Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context)
        {
            string clientId = string.Empty;
            string clientSecret = string.Empty;
            Client client = null;

            if (!context.TryGetBasicCredentials(out clientId, out clientSecret))
            {
                context.TryGetFormCredentials(out clientId, out clientSecret);
            }

            if (context.ClientId == null)
            {
                context.SetError("invalid_clientId", "ClientId is null");
                context.Rejected();
                return;
            }

            using (var clientService = new ClientService(new ClientRepository()))
            {
                client = await clientService.FindClient(context.ClientId);
            }

            if (string.IsNullOrWhiteSpace(clientSecret))
            {
                context.SetError("invalid_clientId", "Client secret should be sent.");
                context.Rejected();
                return;
            }
            else
            {
                if (client == null || client.Secret != SecurityManager.Instance.GetHash(clientSecret))
                {
                    context.SetError("invalid_clientId", "Client secret is invalid.");
                    context.Rejected();
                    return;
                }
            }

            if (!client.Active)
            {
                context.SetError("invalid_clientId", "Client is inactive.");
                context.Rejected();
                return;
            }

            context.OwinContext.Set<string>("as:client_id", context.ClientId);
            context.OwinContext.Set<string>("as:clientAllowedOrigin", client.AllowedOrigin);
            context.OwinContext.Set<string>("as:clientRefreshTokenLifeTime", client.RefreshTokenLifeTime.ToString());

            context.Validated();
        }

        public override Task GrantClientCredentials(OAuthGrantClientCredentialsContext context)
        {
            var oAuthIdentity = new ClaimsIdentity(context.Options.AuthenticationType);
            //oAuthIdentity.AddClaim(new Claim(ClaimTypes.Name, "Anonymous"));

            var props = new AuthenticationProperties(new Dictionary<string, string>
                {
                    { "as:client_id", context.ClientId }
                });
            var ticket = new AuthenticationTicket(oAuthIdentity, props);
            context.Validated(ticket);

            return base.GrantClientCredentials(context);
        }

        public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
        {
            //是否允许跨域请求
            var allowedOrigin = context.OwinContext.Get<string>("as:clientAllowedOrigin");
            if (allowedOrigin == null) allowedOrigin = "*";
            context.OwinContext.Response.Headers.Add("Access-Control-Allow-Origin", new[] { allowedOrigin });

            //if (context.UserName != context.Password)
            //{
            //    context.Rejected();
            //    return;
            //}

            //查找登录用户
            User user = null;
            using (IAccountService accountService = new AccountService<IUserRepository>(new UserRepository())) 
            {
                user = await accountService.FindUser(context.UserName, SecurityManager.Instance.Md5AndBase64Encrypt(context.Password));
                if (user == null) 
                {
                    context.SetError("invalid_grant", "用户名密码错误");
                    context.Rejected();
                }
            }

            var roleName = Enum.GetName(typeof(RoleTypes), user.RoleId);

            //获取客户端传递的额外数据，store30Days表明是否保存用户token30天
            var data = await context.Request.ReadFormAsync();
            if (!string.IsNullOrWhiteSpace(data["store30Days"]) && bool.Parse(data["store30Days"]))
            {
                context.Options.AccessTokenExpireTimeSpan = TimeSpan.FromDays(30);
            }

            foreach (var item in data)
            {
                Debug.WriteLine(string.Format("{0},{1}", item.Key, item.Value));
            }

            // create identity
            var id = new ClaimsIdentity(context.Options.AuthenticationType);
            ///id.AddClaim(new Claim("sub", context.UserName));
            id.AddClaim(new Claim(ClaimTypes.Name, context.UserName));
            id.AddClaim(new Claim(ClaimTypes.Role, roleName));

            ClaimsPrincipal principal = new ClaimsPrincipal(id);
            HttpContext.Current.User = principal;

            // create metadata to pass on to refresh token provider
            var props = new AuthenticationProperties(new Dictionary<string, string>
                {
                    { "as:client_id", context.ClientId }
                });

            var ticket = new AuthenticationTicket(id, props);
            context.Validated(ticket);
        }

        public override async Task GrantRefreshToken(OAuthGrantRefreshTokenContext context)
        {
            var originalClient = context.Ticket.Properties.Dictionary["as:client_id"];
            var currentClient = context.ClientId;

            // enforce client binding of refresh token
            if (originalClient != currentClient)
            {
                context.SetError("invalid_clientId", "Refresh token is issued to a different clientId.");
                context.Rejected();
                return;
            }

            // chance to change authentication ticket for refresh token requests
            var newId = new ClaimsIdentity(context.Ticket.Identity);
            newId.AddClaim(new Claim("newClaim", "refreshToken"));

            var newTicket = new AuthenticationTicket(newId, context.Ticket.Properties);
            context.Validated(newTicket);
        }

        public override Task TokenEndpoint(OAuthTokenEndpointContext context)
        {
            foreach (KeyValuePair<string, string> property in context.Properties.Dictionary)
            {
                context.AdditionalResponseParameters.Add(property.Key, property.Value);
            }
            return Task.FromResult<object>(null);
        }
    }
}